﻿Imports System.ComponentModel
Imports System.ComponentModel.Design.Serialization
Imports System.CodeDom
Imports System.ComponentModel.Design
Imports System.Windows.Forms

''' <summary>
''' When added to user control or form it will replace the standard ComponentResourceManager with DbComponentResourceManager.
''' </summary>
<Description("When added to user control or form it will replace the standard ComponentResourceManager with DbComponentResourceManager")> _
<Designer(GetType(LocalizerComponentDesigner))> _
<DesignerSerializer(GetType(LocalizerComponentSerializer), GetType(CodeDomSerializer))> _
Public NotInheritable Class LocalizerComponent
    Inherits Component

    Private _resourceManager As LocalizerComponentResourceManager
    Private _owner As Component
    Private _type As Type

    Sub New()
        _additionalTranslations = New AdditionalLocalizerEntryCollection(Me)
    End Sub

    <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
    <Browsable(False)> _
    Public ReadOnly Property Localizer As Localizer
        Get
            Return _resourceManager.Localizer
        End Get
    End Property


    ''' <summary>
    ''' Overrides the given resource manager with a new one.
    ''' </summary>
    ''' <param name="type">The type.</param>
    ''' <param name="owner">Owner control for which the resource manager will be instantiated</param>
    ''' <param name="resourceManager">The resource manager.</param>
    Public Sub OverrideResourceManager(type As Type, owner As Component, ByRef resourceManager As ComponentResourceManager)
        If Me.DesignMode Then Exit Sub
        _owner = owner
        _type = type
        _resourceManager = New LocalizerComponentResourceManager(type)
        _resourceManager.LoadTranslations()
        resourceManager = _resourceManager
    End Sub

    Public Sub AssignAdditionalLocalizerVariable(key As String, defaultValue As String, ByRef variable As String)
        variable = Localizer(_owner.GetType().FullName & "." & key, defaultValue)
    End Sub

    ''' <summary>
    ''' Gets the resource manager NLS entries.
    ''' </summary>
    ''' <returns></returns>
    Public Function GetResourceManagerLocalizer() As Localizer
        Return _resourceManager.Localizer
    End Function

    ''' <summary>
    ''' Shows the resource manager entries.
    ''' </summary>
    Public Sub ShowTranslationManagerWindow(recurseControls As Boolean)
        Dim allLocalizer As New List(Of Localizer)
        If recurseControls AndAlso TypeOf _owner Is Control Then
            CollectLocalizerInControls(CType(_owner, Control), True, allLocalizer)
        Else
            allLocalizer.Add(_resourceManager.Localizer)
        End If
        If allLocalizer.Count = 0 Then Exit Sub
        Dim dlg As New dlgTranslationManagement(_type.FullName, Localizer.Merge(allLocalizer.ToArray()))
        dlg.ShowDialog()
    End Sub

    ''' <summary>
    ''' Shows the resource manager entries.
    ''' </summary>
    Public Shared Sub ShowTranslationManagerWindow(ctrl As Control, recurseControls As Boolean)
        Dim allLocalizer As New List(Of Localizer)
        CollectLocalizerInControls(ctrl, recurseControls, allLocalizer)
        If allLocalizer.Count = 0 Then Exit Sub
        Dim dlg As New dlgTranslationManagement(ctrl.GetType().FullName, Localizer.Merge(allLocalizer.ToArray()))
        dlg.ShowDialog()
    End Sub


    Private Shared Sub CollectLocalizerInControls(ctrl As Control, recurseControls As Boolean, allLocalizer As List(Of Localizer))
        Dim localizerComponents = From field As System.Reflection.FieldInfo In ctrl.GetType().GetFields(Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Public)
                            Where field.FieldType Is GetType(LocalizerComponent)
                            Select field.GetValue(ctrl)
        For Each localizerComponent As LocalizerComponent In localizerComponents
            If Not allLocalizer.Contains(localizerComponent.Localizer) Then
                allLocalizer.Add(localizerComponent.Localizer)
            End If
        Next
        For Each child As Control In ctrl.Controls
            'If TypeOf child Is DevExpress.XtraPrinting.Preview.DocumentViewer Then
            '    Dim docViewer = CType(child, DevExpress.XtraPrinting.Preview.DocumentViewer)
            '    If TypeOf docViewer.DocumentSource Is ReportTranslatable Then
            '        nlsComponents = From field As System.Reflection.FieldInfo In docViewer.DocumentSource.GetType().GetFields(Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Public)
            '                Where field.FieldType Is GetType(LocalizerComponent)
            '                Select field.GetValue(docViewer.DocumentSource)
            '        For Each nlsComponent As LocalizerComponent In nlsComponents
            '            If Not allLocalizer.Contains(nlsComponent.Localizer) Then
            '                allLocalizer.Add(nlsComponent.Localizer)
            '            End If
            '        Next
            '    End If
            'Else
            CollectLocalizerInControls(child, recurseControls, allLocalizer)
            'End If
        Next
    End Sub


    <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
    <Description("Additional translations")> _
    Public ReadOnly Property AdditionalTranslations As AdditionalLocalizerEntryCollection
        Get
            Return _additionalTranslations
        End Get
    End Property
    Private _additionalTranslations As AdditionalLocalizerEntryCollection


    <ListBindable(False)>
    Public Class AdditionalLocalizerEntryCollection
        Inherits CollectionBase


        Private _localizerComponent As LocalizerComponent

        Sub New(localizerComponent As LocalizerComponent)
            _localizerComponent = localizerComponent
        End Sub


        <Description("Provides indexed access to individual entries.")>
        Default Public ReadOnly Property Item(index As Integer) As AdditionalLocalizerEntry
            Get
                Return CType(MyBase.List(index), AdditionalLocalizerEntry)
            End Get
        End Property


        Public Overridable Sub AddRange(entries As AdditionalLocalizerEntry())
            For i As Integer = 0 To entries.Length - 1
                Dim value As AdditionalLocalizerEntry = entries(i)
                MyBase.List.Add(value)
            Next
        End Sub

        Public Sub Insert(index As Integer, entry As AdditionalLocalizerEntry)
            If entry.Name Is Nothing Then entry.Name = GetNextKey()
            MyBase.List.Insert(index, entry)
        End Sub

        Public Overridable Function Insert(index As Integer) As AdditionalLocalizerEntry
            If index < 0 Then
                index = 0
            End If
            If index >= MyBase.Count Then
                Return Me.Add()
            End If
            Dim entry As New AdditionalLocalizerEntry
            If entry.Name Is Nothing Then entry.Name = GetNextKey()
            MyBase.List.Insert(index, entry)
            Return entry
        End Function

        Public Overridable Function Add(entry As AdditionalLocalizerEntry) As Integer
            If entry.Name Is Nothing Then entry.Name = GetNextKey()
            Return MyBase.List.Add(entry)
        End Function

        Public Overridable Function Add() As AdditionalLocalizerEntry
            Dim entry As New AdditionalLocalizerEntry
            If entry.Name Is Nothing Then entry.Name = GetNextKey()
            MyBase.List.Add(entry)
            Return entry
        End Function

        Public Overridable Sub Remove(entry As AdditionalLocalizerEntry)
            If Not MyBase.List.Contains(entry) Then
                Return
            End If
            MyBase.List.Remove(entry)
        End Sub

        Public Overridable Function Contains(entry As AdditionalLocalizerEntry) As Boolean
            Return MyBase.List.Contains(entry)
        End Function

        Public Overridable Function IndexOf(entry As AdditionalLocalizerEntry) As Integer
            Return MyBase.List.IndexOf(entry)
        End Function

        'Protected Overrides Sub OnValidate(value As Object)
        '    Dim entry = CType(value, AdditionalLocalizerEntry)
        '    If Me.List.Cast(Of AdditionalLocalizerEntry).Where(Function(other) entry IsNot other).Any(Function(other) other.Name = entry.Name) Then
        '        Me.Remove(entry)
        '        Throw New ArgumentException("You may not add the same key twice")
        '    End If
        'End Sub

        Protected Overrides Sub OnInsert(index As Integer, value As Object)
            Dim entry = CType(value, AdditionalLocalizerEntry)
            If entry.Name Is Nothing Then entry.Name = GetNextKey()
            MyBase.OnInsert(index, value)
        End Sub

        Protected Overrides Sub OnSet(index As Integer, oldValue As Object, newValue As Object)
            Dim entry = CType(newValue, AdditionalLocalizerEntry)
            If entry.Name Is Nothing Then entry.Name = GetNextKey()
            MyBase.OnSet(index, oldValue, newValue)
        End Sub

        Private Function GetNextKey() As String
            Dim i As Integer = 1
            Dim defKey = "Message"
            While Me.OfType(Of AdditionalLocalizerEntry).Any(Function(ane) ane.Name = defKey & i)
                i += 1
            End While
            Return defKey & i
        End Function

    End Class

End Class



<DesignTimeVisible(False), ToolboxItem(False), TypeConverter(GetType(ExpandableObjectConverter))> _
Public Class AdditionalLocalizerEntry
    Implements IComponent


    <Browsable(False), DefaultValue("")>
    Public Overridable Property Name() As String
        Get
            If Me.Site IsNot Nothing Then
                Me._name = Me.Site.Name
            End If
            Return Me._name
        End Get
        Set(value As String)
            If value Is Nothing Then
                value = String.Empty
            End If
            Me._name = value
            If Me.Site IsNot Nothing Then
                Me.Site.Name = Me._name
            End If
        End Set
    End Property
    Private _name As String = String.Empty

    <Localizable(True)> _
    <Description("Default value which can be translated")> _
    <Editor(GetType(MultilineStringEditor), GetType(System.Drawing.Design.UITypeEditor))> _
    Public Property Value As String

    Public Event Disposed(sender As Object, e As System.EventArgs) Implements System.ComponentModel.IComponent.Disposed

    <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
    Public Property Site As System.ComponentModel.ISite Implements System.ComponentModel.IComponent.Site

    Public Sub Dispose() Implements IDisposable.Dispose
        RaiseEvent Disposed(Me, EventArgs.Empty)
    End Sub


End Class



''' <summary>
''' CodeDom serializer for LocalizerComponent component.
''' </summary>
''' <remarks></remarks>
Public Class LocalizerComponentSerializer
    Inherits CodeDomSerializer

    ''' <summary>
    ''' Deserializes the specified serialized CodeDOM object into an object.
    ''' </summary>
    ''' <param name="manager">A serialization manager interface that is used during the deserialization process.</param>
    ''' <param name="codeObject">A serialized CodeDOM object to deserialize.</param>
    ''' <returns>
    ''' The deserialized CodeDOM object.
    ''' </returns>
    ''' <exception cref="T:System.ArgumentNullException">
    '''   <paramref name="manager" /> or <paramref name="codeObject" /> is null.</exception>
    '''   <exception cref="T:System.ArgumentException">
    '''   <paramref name="codeObject" /> is an unsupported code element.</exception>
    Public Overrides Function Deserialize(manager As System.ComponentModel.Design.Serialization.IDesignerSerializationManager, codeObject As Object) As Object
        ' remove additional statements because they should not be used by designer
        Dim statements = TryCast(codeObject, CodeStatementCollection)
        If statements IsNot Nothing Then
            Dim statementsToRemove = From expr In statements.OfType(Of CodeExpressionStatement)()
                                     Let invoke = TryCast(expr.Expression, CodeMethodInvokeExpression)
                                     Where invoke IsNot Nothing And (invoke.Method.MethodName = "OverrideResourceManager" OrElse _
                                                                     invoke.Method.MethodName = "AssignAdditionalLocalizerVariable" OrElse _
                                                                     invoke.Method.MethodName = "ApplyResources")
                                    Select expr
            For Each stmt In statementsToRemove.ToList()
                statements.Remove(stmt)
            Next
        End If
        Dim baseSerializer = CType(manager.GetSerializer(GetType(Component), GetType(CodeDomSerializer)), CodeDomSerializer)
        Return baseSerializer.Deserialize(manager, codeObject)
    End Function

    ''' <summary>
    ''' Serializes the specified object into a CodeDOM object.
    ''' </summary>
    ''' <param name="manager">The serialization manager to use during serialization.</param>
    ''' <param name="value">The object to serialize.</param>
    ''' <returns>
    ''' A CodeDOM object representing the object that has been serialized.
    ''' </returns>
    ''' <exception cref="T:System.ArgumentNullException">
    '''   <paramref name="manager" /> or <paramref name="value" /> is null.</exception>
    Public Overrides Function Serialize(manager As System.ComponentModel.Design.Serialization.IDesignerSerializationManager, value As Object) As Object
        Dim baseSerializer = CType(manager.GetSerializer(GetType(Component), GetType(CodeDomSerializer)), CodeDomSerializer)
        Dim codeObject = baseSerializer.Serialize(manager, value)
        If Not (TypeOf codeObject Is CodeStatementCollection) Then Return codeObject
        Dim nlsComponent = CType(value, LocalizerComponent)
        Dim statements = CType(codeObject, CodeStatementCollection)
        Dim dh = CType(manager.GetService(GetType(IDesignerHost)), IDesignerHost)
        Dim nlsField As New CodeFieldReferenceExpression(New CodeThisReferenceExpression(), nlsComponent.Site.Name)


        ' add code statement to 'take over' the default resource manager
        Dim ctd = CType(manager.GetService(GetType(CodeTypeDeclaration)), CodeTypeDeclaration)
        Dim typeofExpression = New CodeTypeOfExpression(ctd.Name)
        Dim outResourceExpression = New CodeDirectionExpression(FieldDirection.Out, New CodeVariableReferenceExpression("resources"))
        Dim methodOverrideInvoke As New CodeMethodInvokeExpression( _
            nlsField, _
            "OverrideResourceManager", typeofExpression, New CodeThisReferenceExpression(), outResourceExpression)
        statements.Insert(0, New CodeExpressionStatement(methodOverrideInvoke))
        Dim initComponentMethod = ctd.Members.OfType(Of CodeMemberMethod).FirstOrDefault(Function(m) m.Name = "InitializeComponent")
        For i As Integer = 0 To initComponentMethod.Statements.Count - 1
            Dim stmt = TryCast(initComponentMethod.Statements(i), CodeExpressionStatement)
            If stmt IsNot Nothing Then
                Dim stmt2 = TryCast(stmt.Expression, CodeMethodInvokeExpression)
                If stmt2 IsNot Nothing AndAlso stmt2.Method.MethodName = "OverrideResourceManager" Then Exit For ' we have reached our own component ... stop
                If stmt2 IsNot Nothing AndAlso stmt2.Method.MethodName = "ApplyResources" Then
                    'statements.Add(stmt)
                    Dim reaplyRes = New CodeExpressionStatement()
                    reaplyRes.Expression = Clone(stmt.Expression)
                    statements.Add(reaplyRes)
                End If
            End If
        Next

        Return codeObject

    End Function


    Private Function Clone(code As CodeExpression) As CodeExpression
        If code Is Nothing Then Return Nothing
        If code.GetType() Is GetType(CodeMethodInvokeExpression) Then
            Dim spec As CodeMethodInvokeExpression = CType(code, CodeMethodInvokeExpression)
            Dim methodRef = Clone(spec.Method)
            Dim params = From p As CodeExpression In spec.Parameters.OfType(Of CodeExpression)()
                         Select Clone(p)
            Return New CodeMethodInvokeExpression(CType(Clone(spec.Method), CodeMethodReferenceExpression), params.ToArray())
        End If
        If code.GetType() Is GetType(CodeThisReferenceExpression) Then
            Return New CodeThisReferenceExpression()
        End If
        If code.GetType() Is GetType(CodeFieldReferenceExpression) Then
            Dim spec As CodeFieldReferenceExpression = CType(code, CodeFieldReferenceExpression)
            Return New CodeFieldReferenceExpression(Clone(spec.TargetObject), spec.FieldName)
        End If
        If code.GetType() Is GetType(CodeVariableReferenceExpression) Then
            Dim spec As CodeVariableReferenceExpression = CType(code, CodeVariableReferenceExpression)
            Return New CodeVariableReferenceExpression(spec.VariableName)
        End If
        If code.GetType() Is GetType(CodeMethodReferenceExpression) Then
            Dim spec As CodeMethodReferenceExpression = CType(code, CodeMethodReferenceExpression)
            Return New CodeMethodReferenceExpression(Clone(spec.TargetObject), spec.MethodName)
        End If
        If code.GetType() Is GetType(CodePrimitiveExpression) Then
            Dim spec As CodePrimitiveExpression = CType(code, CodePrimitiveExpression)
            Return New CodePrimitiveExpression(spec.Value)
        End If
        If code.GetType() Is GetType(CodePropertyReferenceExpression) Then
            Dim spec As CodePropertyReferenceExpression = CType(code, CodePropertyReferenceExpression)
            Return New CodePropertyReferenceExpression(Clone(spec.TargetObject), spec.PropertyName)
        End If
        Throw New NotSupportedException("Code expression " & code.GetType().Name & " is not supported")
    End Function



End Class

''' <summary>
''' Designer for LocalizerComponent component.
''' </summary>
Public Class LocalizerComponentDesigner
    Inherits ComponentDesigner

    ''' <summary>
    ''' Initializes the specified component designer.
    ''' </summary>
    ''' <param name="c">The c.</param>
    Public Overrides Sub Initialize(c As System.ComponentModel.IComponent)
        MyBase.Initialize(c)
        Dim dh = CType(GetService(GetType(IDesignerHost)), IDesignerHost)
        If dh Is Nothing Then Exit Sub

        ' prevent adding the LocalizerComponent twice on the form
        For Each comp As Component In dh.Container.Components
            If comp IsNot c AndAlso comp.GetType() Is GetType(LocalizerComponent) Then
                Throw New InvalidOperationException("You may not add the LocalizerComponent to the user control/form more then once!")
            End If
        Next

        ' set the 'Localizable' property of the Form/Control to True
        Dim tdp = TypeDescriptor.GetProvider(dh.RootComponent)
        Dim td = tdp.GetExtendedTypeDescriptor(dh.RootComponent)
        Dim propDesc = CType(td.GetProperties()("Localizable"), PropertyDescriptor)
        If propDesc IsNot Nothing AndAlso Not CBool(propDesc.GetValue(dh.RootComponent)) Then
            propDesc.SetValue(dh.RootComponent, True)
        End If

        ' ensure that the component is first
        Dim innerListProperty = dh.Container.Components.GetType().GetProperty("InnerList", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.FlattenHierarchy)
        Dim innerList = TryCast(innerListProperty.GetValue(dh.Container.Components, Nothing), System.Collections.ArrayList)
        If innerList IsNot Nothing AndAlso innerList.IndexOf(c) > 1 Then
            innerList.Remove(c)
            innerList.Insert(0, c)
        End If

    End Sub



End Class
